package com.tien.utils.mail;
/**
 * @author Tien  E-mail:g.tianxin@gmail.com
 * @version Time：2011-6-28 下午11:10:25
 */
import java.net.URL;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;

import org.apache.commons.mail.Email;
import org.apache.commons.mail.EmailAttachment;
import org.apache.commons.mail.EmailException;
import org.apache.commons.mail.HtmlEmail;
import org.apache.commons.mail.SimpleEmail;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Mailer {
  private static Mailer instance;

  private static final Logger log = LoggerFactory.getLogger(Mailer.class);

  public static synchronized Mailer getInstance() {
    if(instance == null) {
      instance = new Mailer();
    }
    return instance;
  }

  private ResourceBundle mailerConfig;

  private RetryQueue retryQueue;

  private String smtpHost;

  private String smtpPass;

  private int smtpPort;

  private String smtpUser;

  private String smtpStartls;
  private Mailer() {
    String configFileClass = this.getClass().getPackage().getName()
        + ".MailerConfig";
    log.debug("loading configuration files from: {}", configFileClass);
    mailerConfig = ResourceBundle.getBundle(configFileClass);

    smtpHost = mailerConfig.getString("mailer.smtp.host");
    smtpPort = Integer.parseInt(mailerConfig.getString("mailer.smtp.port"));
    smtpUser = mailerConfig.getString("mailer.smtp.user");
    smtpPass = mailerConfig.getString("mailer.smtp.pass");
    smtpStartls =  mailerConfig.getString("mail.smtp.starttls.enable");

    log
        .debug(
            "Configured mailer with the following configuration: \nhost={}\nport={}\nuser={}\npass={}",
            new Object[] { smtpHost, smtpPort, smtpUser, smtpPass });

    boolean retryOnFailure = Boolean.parseBoolean(mailerConfig
        .getString("mailer.retryOnFailure"));

    if (retryOnFailure) {
      int retries = Integer.parseInt(mailerConfig
          .getString("retryQueue.retries"));
      int retryInterval = Integer.parseInt(mailerConfig
          .getString("retryQueue.retryInterval"));
      retryQueue = new RetryQueue();
      retryQueue.setRetries(retries);
      retryQueue.setRetryInterval(retryInterval);
      retryQueue.start();
      log
          .debug(
              "Mailer is set to retry on failure, configured the retry queue with the following configuration: retries={}\ninterval={}",
              retries, retryInterval);
    }
  }

  private void configureAttachment(EmailAttachment attachment,
      String disposition, String name, String description) {
    attachment.setDescription(description);
    attachment.setDisposition(disposition);
    attachment.setName(name);
  }

  private void configureEmail(Email email, String senderAddress,
      String senderName, Map<String, String> recipients, String subject)
      throws EmailException {
    log
        .debug(
            "Configuring email with the following information:\nSender Address: {}\nSender Name: {}\nSMTP Host: {}\nSMTP Port: {}\nSMTP User: {}\nSMTP Password: {}",
            new Object[] { senderAddress, senderName, smtpHost, smtpPort,
                smtpUser, smtpPass });

    email.setHostName(smtpHost);
    email.setSmtpPort(smtpPort);
    email.setAuthentication(smtpUser, smtpPass);
    email.setTLS(Boolean.parseBoolean(smtpStartls));
    email.setFrom(senderAddress, senderName);
    email.setSubject(subject);
    for (String emailAddress : recipients.keySet()) {
      email.addTo(emailAddress, recipients.get(emailAddress));
    }
  }

  /**
   * Creates an Email attachment by its path.
   *
   * @param path
   *          path of the attachment.
   * @param name
   *          name of the attachment.
   * @param description
   *          description of the attachment.
   * @return an {@code EmailAttachment} instance.
   */
  public EmailAttachment createAttachmentByPath(String path, String name,
      String description) {
    EmailAttachment attachment = new EmailAttachment();
    configureAttachment(attachment, Email.ATTACHMENTS, name, description);
    attachment.setPath(path);
    return attachment;
  }

  /**
   * Creates an Email attachment by its URL.
   *
   * @param url
   *          URL of the attachment.
   * @param name
   *          name of the attachment.
   * @param description
   *          description of the attachment.
   * @return an {@code EmailAttachment} instance.
   */
  public EmailAttachment createAttachmentByUrl(URL url, String name,
      String description) {
    EmailAttachment attachment = new EmailAttachment();
    configureAttachment(attachment, Email.ATTACHMENTS, name, description);
    attachment.setURL(url);
    return attachment;
  }

  /**
   * Creates a complex email message. This allows more fine-grained control of
   * the email message created. The created email can be sent using the
   * {@link #sendEmail(Email)} method.
   *
   * @param senderAddress
   *          Sender's email address.
   * @param senderName
   *          Sender's name.
   * @param recipients
   *          Recipients, this is a email address -> name map, This is done
   *          delibrately so that we don't accidentally send multiple emails to
   *          the same address.
   * @param subject
   *          Subject of the email.
   * @param textMessage
   *          Text Email body.
   * @param htmlMessage
   *          HTML email body.
   * @return a {@code HtmlEmail} instance.
   */
  public HtmlEmail createComplexEmail(String senderAddress, String senderName,
      Map<String, String> recipients, String subject, String textMessage,
      String htmlMessage) {
    HtmlEmail email = null;
    try {
      email = new HtmlEmail();
      configureEmail(email, senderAddress, senderName, recipients, subject);
      email.setTextMsg(textMessage);

      if (htmlMessage != null) {
        email.setHtmlMsg(htmlMessage);
      }
    } catch (EmailException e) {
      log.error("Failed to construct email", e);
      return null;
    }

    return email;
  }

  /**
   * Creates a simple email. This allows more fine-grained control of the email
   * messages created. The created email should be sent using the
   * {@link #sendEmail(Email)} method.
   *
   * @param senderAddress
   *          Sender's email address.
   * @param senderName
   *          Sender's name.
   * @param recipients
   *          Recipients, this is a email address -> name map, This is done
   *          delibrately so that we don't accidentally send multiple emails to
   *          the same address.
   * @param subject
   *          Subject of the email.
   * @param textMessage
   *          Text Email body.
   * @return a {@code SimpleEmail} instance.
   */
  public SimpleEmail createSimpleEmail(String senderAddress, String senderName,
      Map<String, String> recipients, String subject, String textMessage) {
    SimpleEmail email = null;

    try {
      email = new SimpleEmail();
      configureEmail(email, senderAddress, senderName, recipients, subject);
      email.setCharset("UTF-8");
      email.setMsg(textMessage);
      log.debug("Attempting to create HTML email to address: {}", email
          .getToAddresses());
    } catch (EmailException e) {
      log.error("Failed to construct email", e);
      return null;
    }

    return email;
  }

  /**
   * Sends an complex email.
   *
   * @param senderAddress
   *          The sender's address.
   * @param senderName
   *          The sender's name.
   * @param recipients
   *          Recipients, this is a email address -> name map, This is done
   *          delibrately so that we don't accidentally send multiple emails to
   *          the same address.
   * @param subject
   *          Subject of the email.
   * @param textMessage
   *          Text email body.
   * @param htmlMessage
   *          HTML email body.
   * @param attachments
   *          Attachments.
   * @return {@code true} if the email is successfully sent, {@code false}
   *         otherwise.
   */
  public boolean sendComplexEmail(String senderAddress, String senderName,
      Map<String, String> recipients, String subject, String textMessage,
      String htmlMessage, List<EmailAttachment> attachments) {
    HtmlEmail email = createComplexEmail(senderAddress, senderName, recipients,
        subject, textMessage, htmlMessage);
    try {
      if (attachments != null) {
        for (EmailAttachment attachment : attachments) {
          email.attach(attachment);
        }
      }
    } catch (EmailException e) {
      log.error("Failed to add attachment, to address: {}", email
          .getToAddresses(), e);
      return false;
    }

    log.debug("Attempting to send HTML email to address: {}", email
        .getToAddresses());

    return sendEmail(email);
  }

  /**
   * Sends the email. This method returns {@code true} if the email can be sent
   * the first attempt, {@code false} otherwise. However if the email failed to
   * be sent the first time, it might still be put into a retry queue depending
   * on the configuration.
   *
   * @param email
   *          the email to be sent.
   * @return {@code true} if the email can be sent the first attempt, {@code
   *         false} otherwise.
   */
  public boolean sendEmail(Email email) {
    try {
      email.send();

      log.debug("Email successfully sent to address: {}", email
          .getToAddresses());
    } catch (EmailException e) {
      log.error("Mail error due to exception.", e);
      log.debug("Putting email to {} in retry queue", email.getToAddresses());
      retryQueue.put(email);

      return false;
    }
    return true;
  }

  /**
   * Sends out an email messages.
   *
   * @param senderAddress
   *          Sender's email address.
   * @param senderName
   *          Sender's name.
   * @param recipients
   *          Recipients, this is a email address -> name map, This is done
   *          delibrately so that we don't accidentally send multiple emails to
   *          the same address.
   * @param subject
   *          Subject of the email.
   * @param textMessage
   *          Text email body.
   * @return {@code true} if the email is successfully sent, {@code false}
   *         otherwise.
   */
  public boolean sendEmail(String senderAddress, String senderName,
      Map<String, String> recipients, String subject, String textMessage) {
    return sendEmail(senderAddress, senderName, recipients, subject,
        textMessage, null, null);
  }

  /**
   * Sends out an email message. This function will send out a plain text email
   * message if {@code htmlMessage} is {@code null}, and a HTML email otherwise.
   *
   * @param senderAddress
   *          Sender's email address.
   * @param senderName
   *          Sender's name.
   * @param recipients
   *          Recipients, this is a email address -> name map, This is done
   *          delibrately so that we don't accidentally send multiple emails to
   *          the same address.
   * @param subject
   *          Subject of the mail.
   * @param textMessage
   *          Text email body.
   * @param htmlMessage
   *          HTML email body.
   * @return {@code true} if the email is successfully sent, {@code false}
   *         otherwise.
   */
  public boolean sendEmail(String senderAddress, String senderName,
      Map<String, String> recipients, String subject, String textMessage,
      String htmlMessage) {
    return sendEmail(senderAddress, senderName, recipients, subject,
        textMessage, htmlMessage, null);
  }

  /**
   * Sends an email messages.
   *
   * @param senderAddress
   *          The sender's address.
   * @param senderName
   *          The sender's name.
   * @param recipients
   *          Recipients, this is a email address -> name map, This is done
   *          delibrately so that we don't accidentally send multiple emails to
   *          the same address.
   * @param subject
   *          Subject of the email.
   * @param textMessage
   *          Text email body.
   * @param htmlMessage
   *          HTML email body.
   * @param attachments
   *          Attachments.
   * @return {@code true} if the email is successfully sent, {@code false}
   *         otherwise.
   */
  public boolean sendEmail(String senderAddress, String senderName,
      Map<String, String> recipients, String subject, String textMessage,
      String htmlMessage, List<EmailAttachment> attachments) {
    if (htmlMessage == null && attachments == null) {
      return sendSimpleEmail(senderAddress, senderName, recipients, subject,
          textMessage);
    } else {
      return sendComplexEmail(senderAddress, senderName, recipients, subject,
          textMessage, htmlMessage, attachments);
    }
  }

  /**
   * Sends a HTML email to the specified address.
   *
   * @param senderAddress
   *          Sender's email address.
   * @param senderName
   *          Sender's name.
   * @param recipients
   *          Recipients, this is a email address -> name map. This is done
   *          deliberately so that we don't accidentally add the same recipient
   *          several times and end up spamming them.
   * @param subject
   *          Email subject.
   * @param textMessage
   *          Text message to be displayed if the user's email client doesn't
   *          support HTML.
   * @param htmlMessage
   *          The HTML email body.
   * @return {@code true} if the email is successfully sent, {@code false}
   *         otherwise.
   * @deprecated see
   *             {@link #sendComplexEmail(String, String, Map, String, String, String, List)}
   *             instead
   */
  @Deprecated
  public boolean sendHtmlEmail(String senderAddress, String senderName,
      Map<String, String> recipients, String subject, String textMessage,
      String htmlMessage) {
    HtmlEmail email = null;
    try {
      email = new HtmlEmail();
      configureEmail(email, senderAddress, senderName, recipients, subject);
      email.setHtmlMsg(htmlMessage);
      email.setTextMsg(textMessage);
    } catch (EmailException e) {
      log.error("Failed to construct email to: {}", email.getToAddresses(), e);
      return false;
    }
    log.debug("Attempting to send HTML email to address: {}", email
        .getToAddresses());
    return sendEmail(email);
  }

  /**
   * Sends a plane text email to the specified address.
   *
   * @param senderAddress
   *          Sender's address.
   * @param senderName
   *          Sender's name.
   * @param recipients
   *          Recipients, this is a email address -> name map. This is done
   *          deliberately so that we don't accidentally add the same recipient
   *          several times and end up spamming them.
   * @param subject
   *          Email subject.
   * @param message
   *          Email message.
   * @return {@code true} if the email is sent correctly, {@code false}
   *         otherwise.
   */
  public boolean sendSimpleEmail(String senderAddress, String senderName,
      Map<String, String> recipients, String subject, String message) {
    SimpleEmail email = createSimpleEmail(senderAddress, senderName,
        recipients, subject, message);

    log.debug("Attempting to send Text email to address: {}", email
        .getToAddresses());

    return sendEmail(email);
  }
}